﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Slam.Configuration;
using Slam.Events;
using Slam.Exceptions;
using Microsoft.SharePoint;
using SlamContext.Cache;

namespace Slam.SharePoint
{
	public static class SPListItemExtender
	{
		/// <summary>
		/// Regex pattern to validate Guid strings
		/// </summary>
		private static Regex _guidRegex = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);

		/// <summary>
		/// Verifies that SPListItems associated with the given SPListItem as contained in the given
		/// list of association fields have values in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="associationFields">Associations to be verified in target data source</param>
		/// <param name="force">Process related ListItems whether or not they are represented in SLAM's target data source</param>
		public static void EnsureSlamAssociations(this SPListItem listItem, List<Field> associationFields, bool force)
		{
			IListItemExtensions implementation = ComponentService.GetInstance<IListItemExtensions>();
			implementation.EnsureSlamAssociations(listItem, associationFields, force);
		}

		/// <summary>
		/// Gets the identifying Title of the given SPListItem.
		/// </summary>
		/// <param name="listItem"></param>
		/// <returns></returns>
		public static string GetTitle(this SPListItem listItem)
		{
			string title = "";
			if (listItem.Fields.ContainsField("Title") && !String.IsNullOrEmpty(Convert.ToString(listItem["Title"])))
			{
				title = listItem.Title;
			}
			else if (listItem.Fields.ContainsField("Name") && !String.IsNullOrEmpty(Convert.ToString(listItem["Name"])))
			{
				title = (string)listItem["Name"];
			} 
			else 
			{
				title = listItem.Name;
			}

			return title;
		}

		/// <summary>
		/// Gets a set of field values for given list of Fields from the given SPListItem.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="outputFields">List of Fields for which to get values</param>
		/// <returns>Hashtable of field names and values</returns>
		public static Hashtable GetFieldValues(this SPListItem listItem, List<Field> outputFields)
		{
			IListItemExtensions implementation = ComponentService.GetInstance<IListItemExtensions>();
			return implementation.GetFieldValues(listItem, outputFields);
		}

		/// <summary>
		/// Gets list of SPListItems associated with the given listItem via the given lookup field name.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="fieldName">Name of the lookup field</param>
		/// <returns>List of associated SPListItems</returns>
		public static List<SPListItem> GetLookupValueListItems(this SPListItem listItem, string fieldName)
		{
			List<SPListItem> lookupListItems = new List<SPListItem>();
			SPField associationField = listItem.Fields.Cast<SPField>().Where(f => f.Title.Equals(fieldName, StringComparison.CurrentCultureIgnoreCase) || f.InternalName.Equals(fieldName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
			if (associationField != null)
			{
				switch (associationField.Type)
				{
					case SPFieldType.Invalid:
					case SPFieldType.Lookup:
						Guid listId = Guid.Empty;
						PropertyDescriptor property = TypeDescriptor.GetProperties(associationField).Find("LookupList", false);

						if (property != null && property.PropertyType == typeof(string))
						{
							string id = Convert.ToString(property.GetValue(associationField));
							if (_guidRegex.IsMatch(id))
							{
								listId = new Guid(id);
							}
						}

						if (listId != Guid.Empty)
						{
							bool listFound = false;
							for (int i = 0; i < listItem.Web.Site.AllWebs.Count; i++)
							{
								using(SPWeb lookupSite = listItem.Web.Site.AllWebs[i])
								{
									SPList lookupList = null;
									try
									{
										lookupList = lookupSite.Lists[listId];
									}
									catch
									{
										//List is not found
									}

									if (lookupList != null)
									{
										if (listItem[associationField.Id] != null)
										{
											SPFieldLookupValueCollection fieldCollectionValue = new SPFieldLookupValueCollection();
											if (listItem[associationField.Id] is SPFieldLookupValueCollection)
											{
												fieldCollectionValue = (SPFieldLookupValueCollection)listItem[associationField.Id];
											}
											else if (listItem[associationField.Id] is SPFieldLookupValue)
											{
												fieldCollectionValue.Add((SPFieldLookupValue)listItem[associationField.Id]);
											}
											else
											{
												try
												{
													fieldCollectionValue = new SPFieldLookupValueCollection(Convert.ToString(listItem[associationField.Id]));
												}
												catch { }
											}

											foreach (SPFieldLookupValue lookupField in fieldCollectionValue)
											{
												if (!String.IsNullOrEmpty(lookupField.LookupValue))
												{
													try
													{
														lookupListItems.Add(lookupList.GetItemById(lookupField.LookupId));
													}

													catch (ArgumentException)
													{
														//The non-existence of an item can only be deteremined
														//when SharePoint throws an exception, there is no check method
														//nor null return value for GetItemById
													}
												}
											}
										}
										listFound = true;
										break;
									}
								}
							}

							if (!listFound)
							{
								throw new SlamException("Lookup list not found.  ID: " + listId.ToString());
							}
						}
						break;
					case SPFieldType.User:
						SPList userLookupList = listItem.Web.SiteUserInfoList;
						if (listItem[associationField.Id] != null)
						{
							SPFieldUserValueCollection fieldCollectionValue = new SPFieldUserValueCollection();
							if (listItem[associationField.Id] is SPFieldUserValueCollection)
							{
								fieldCollectionValue = (SPFieldUserValueCollection)listItem[associationField.Id];
							}
							else if (listItem[associationField.Id] is SPFieldLookupValue)
							{
								fieldCollectionValue.Add((SPFieldUserValue)listItem[associationField.Id]);
							}
							else
							{
								try
								{
									fieldCollectionValue = new SPFieldUserValueCollection(listItem.Web, Convert.ToString(listItem[associationField.Id]));
								}
								catch { }
							}

							foreach (SPFieldUserValue userField in (SPFieldUserValueCollection)listItem[associationField.Id])
							{
								if (!String.IsNullOrEmpty(userField.LookupValue))
								{
									try
									{
										lookupListItems.Add(userLookupList.GetItemById(userField.LookupId));
									}

									catch (ArgumentException)
									{
										//The non-existence of an item can only be deteremined
										//when SharePoint throws an exception, there is no check method
										//nor null return value for GetItemById
									}
								}
							}
						}
						break;
				}
			}
			return lookupListItems;
		}

		/// <summary>
		/// Gets the ConfiguredType for the given ListItem if it is configured to be managed by SLAM.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <returns>Configured Type from SLAM Configuration</returns>
		public static ConfiguredType GetSlamConfiguredType(this SPListItem listItem)
		{
			ConfiguredType configuredType = null;
			try
			{
				if (SlamContext.Cache.ContainsKey(Constants.Cache.ConfiguredTypeCollection, CacheScope.Application))
				{
					var types = SlamContext.Cache.Get<Dictionary<string, ConfiguredType>>(Constants.Cache.ConfiguredTypeCollection);
					if (types.ContainsKey(listItem.ParentList.ID.ToString()))
						configuredType = types[listItem.ParentList.ID.ToString()];
					else if (listItem.ContentType != null && types.ContainsKey(listItem.ContentType.Parent.Id.ToString()))
						configuredType = types[listItem.ContentType.Parent.Id.ToString()];
				}
			}
			catch { }

			if (configuredType == null)
			{
				IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
				DataMapping currentDataMapping = slamConfiguration.DataMapping;
				string siteCollection = listItem.Web.Site.ServerRelativeUrl.TrimStart('/');
				if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
				{
					currentDataMapping = slamConfiguration.DataMappings[siteCollection];
				}
				else if (String.IsNullOrEmpty(siteCollection) && currentDataMapping != slamConfiguration.DataMappings["DEFAULTMAPPING"])
				{
					currentDataMapping = slamConfiguration.DataMappings["DEFAULTMAPPING"];
				}


				bool configuredContentType = false;
				foreach (SPEventReceiverDefinition receiver in listItem.ParentList.EventReceivers)
				{
					if (receiver.Name.StartsWith("SLAM") && receiver.Class.Contains("ContentTypeEvent"))
					{
						configuredContentType = true;
						break;
					}
				}

				SPContentType contentType = listItem.ContentType;

				foreach (ConfiguredType type in currentDataMapping.GetAllTypes())
				{
					bool contentTypeFound = false;
					if ((configuredContentType && contentType == null) || (contentType != null && contentType.Name.Equals("Item")))
					{
						foreach (SPContentType listContentType in listItem.ParentList.ContentTypes)
						{
							if (listContentType.EqualsOrInherits(type.Name))
							{
								contentTypeFound = true;
								break;
							}
						}
					}
					else if (contentType != null && configuredContentType && contentType.EqualsOrInherits(type.Name))
					{
						contentTypeFound = true;
					}

					if (configuredContentType && type.ContentType && contentTypeFound)
					{
						configuredType = type;
						break;
					}
					else if (!configuredContentType && (type.Id == listItem.ParentList.ID.ToString() || type.Name == listItem.ParentList.Title))
					{
						configuredType = type;
						break;
					}
				}
			}

			return configuredType;
		}

		/// <summary>
		/// Gets the unique identifier of the given SPListItem.  The ListItem can be a new list item that has not yet been assigned an ID.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <returns>Unique List Item identifier</returns>
		public static object GetUniqueId(this SPListItem listItem)
		{
			return GetUniqueId(listItem, false, true);
		}

		/// <summary>
		/// Gets the unique identifier of the given SPListItem.  The ListItem can be a new list item that has not yet been assigned an ID.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="generateNewIdIfNull">Indicates whether a new ID should be generated if the given ListItem does not have a relevant ID</param>
		/// <param name="throwExceptionIfNull">Indicates whether exceptions should be thrown on error</param>
		/// <returns>Unique List Item identifier</returns>
		public static object GetUniqueId(this SPListItem listItem, bool generateNewIdIfNull, bool throwExceptionIfNull)
		{	
			IListItemExtensions implementation = ComponentService.GetInstance<IListItemExtensions>();
			return implementation.GetUniqueId(listItem, generateNewIdIfNull, throwExceptionIfNull);
		}

		private static Regex slamPropertyFormat = new Regex("(?<=##)(.*?)(?=##)", RegexOptions.Compiled);

		public static string GetSlamPropertyValue(this SPListItem listItem, string name)
		{
			IListItemExtensions implementation = ComponentService.GetInstance<IListItemExtensions>();
			return implementation.GetSlamPropertyValue(listItem, name);
		}

		public static void SetSlamProperty(this SPListItem listItem, string name, object value)
		{
			IListItemExtensions implementation = ComponentService.GetInstance<IListItemExtensions>();
			implementation.SetSlamProperty(listItem, name, value);
		}

		/// <summary>
		/// Deletes item from SLAM target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <returns>Indication whether the delete succeeded</returns>
		public static bool SlamDelete(this SPListItem listItem)
		{
			return SlamDelete(listItem, true);
		}

		/// <summary>
		/// Deletes item from SLAM target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether the delete process should run with elevated priveleges</param>
		/// <returns>Indication whether the delete succeeded</returns>
		public static bool SlamDelete(this SPListItem listItem, bool runWithElevatedPriveleges)
		{
			return SlamDelete(listItem, runWithElevatedPriveleges, false);
		}

		/// <summary>
		/// Deletes item from SLAM target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether the delete process should run with elevated priveleges</param>
		/// <param name="deleteItem">Indicates whether the given SPListItem should be deleted from SharePoint as well</param>
		/// <returns>Indication whether the delete succeeded</returns>
		public static bool SlamDelete(this SPListItem listItem, bool runWithElevatedPriveleges, bool deleteItem)
		{
			return SlamDelete(listItem, runWithElevatedPriveleges, false, (new SPFieldLookupValue(listItem["Modified By"].ToString())).LookupValue);
		}

		/// <summary>
		/// Deletes item from SLAM target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether the delete process should run with elevated priveleges</param>
		/// <param name="deleteItem">Indicates whether the given SPListItem should be deleted from SharePoint as well</param>
		/// <param name="updateUserName">Specific username used to "delete" the given ListItem</param>
		/// <returns>Indication whether the delete succeeded</returns>
		public static bool SlamDelete(this SPListItem listItem, bool runWithElevatedPriveleges, bool deleteItem, string updateUserName)
		{
			bool returned = true;
			EventUtility eventUtility = new EventUtility();
			eventUtility.DisableEventHandlers();

			SPSite siteCollection = null;
			SPWeb site = listItem.Web;
			SPList list = null;
			SPListItem listItemToUpdate = listItem;
			bool disposeSites = false;

			if (runWithElevatedPriveleges)
			{
				SPSecurity.RunWithElevatedPrivileges(delegate()
				{
					bool itemFound = false;

					siteCollection = new SPSite(site.Site.ID);
					site = siteCollection.AllWebs[site.ID];

					list = site.Lists[listItem.ParentList.ID];

					try
					{
						listItemToUpdate = list.GetItemById(listItem.ID);
						itemFound = true;
						list.AddSlamPropertiesField();
					}
					catch { }

					disposeSites = true;

					if (itemFound)
					{
						listItemToUpdate["SLAM Properties"] = listItem["SLAM Properties"];
						//foreach (SPField field in listItem.Fields)
						//{
						//    if (!field.ReadOnlyField)
						//    {
						//        listItemToUpdate[field.Id] = listItem[field.Id];
						//    }
						//}

						//if (listItemToUpdate.ModerationInformation != null)
						//{
						//    listItemToUpdate.ModerationInformation.Status = listItem.ModerationInformation.Status;
						//}
					}
				});
			}
			else
			{
				siteCollection = site.Site;
				list = listItem.ParentList;
			}

			SlamItemContext.ListItem = listItemToUpdate;
			SlamItemContext.UserLoginName = updateUserName;

			//Get the SLAM configuration
			IConfigurationManager configuration = ComponentService.GetInstance<IConfigurationManager>();

			ConfiguredType configuredType = listItemToUpdate.GetSlamConfiguredType();
			SlamItemContext.ProcessedByContentType = configuredType.ContentType;

			if (configuredType.ContentType)
			{
				string contentTypeName = listItemToUpdate.ContentType.Name;
				if (configuredType != null)
				{
					contentTypeName = configuredType.Name;
				}
				SlamItemContext.TypeName = contentTypeName;
			}
			else
			{
				SlamItemContext.TypeName = list.Title;
			}

			IItemDeleter deleter = ComponentService.GetInstance<IItemDeleter>(siteCollection.ServerRelativeUrl.TrimStart('/'), site.Name, SlamItemContext.TypeName);
			returned = deleter.Delete(listItemToUpdate);

			//If the given ListItem is a Folder, delete its children
			if (listItemToUpdate.Folder != null)
			{
				Dictionary<string, string> deleteItemTypes = new Dictionary<string, string>();
				DeleteFolder(siteCollection.ServerRelativeUrl.TrimStart('/'), site.Name, listItemToUpdate, deleteItemTypes);
			}

			if (runWithElevatedPriveleges && deleteItem)
			{
				listItemToUpdate.Delete();
			}
			else if (deleteItem)
			{
				listItem.Delete();
			}

			if (disposeSites)
			{
				site.Dispose();
				siteCollection.Dispose();
			}

			eventUtility.EnableEventHandlers();
			return returned;
		}

		private static void DeleteFolder(string siteCollection, string siteName, SPListItem item, Dictionary<string, string> deleteItemTypes)
		{
			if (item != null)
			{
				SPFolder folder = item.Folder;
				if (folder != null)
				{
					foreach (SPFolder subFolder in folder.SubFolders)
					{
						DeleteFolder(siteCollection, siteName, subFolder.Item, deleteItemTypes);
						DeleteFolderItem(siteCollection, siteName, subFolder.Item, deleteItemTypes);
					}

					foreach (SPFile file in folder.Files)
					{
						DeleteFolderItem(siteCollection, siteName, file.Item, deleteItemTypes);
					}
				}
			}
		}

		private static void DeleteFolderItem(string siteCollection, string siteName, SPListItem item, Dictionary<string, string> deleteItemTypes)
		{
			if (item != null)
			{
				string typeName = "";
				SlamItemContext.ListItem = item;

				if (item.ContentType != null)
				{
					if (deleteItemTypes.ContainsKey(item.ContentType.Name))
					{
						typeName = deleteItemTypes[item.ContentType.Name];
					}
					else if (item.ContentType.EventReceivers != null)
					{
						foreach (SPEventReceiverDefinition receiver in item.ContentType.EventReceivers)
						{
							if (receiver.Name == "SLAM" + siteName + item.ContentType.Name + Microsoft.SharePoint.SPEventReceiverType.ItemDeleting.ToString())
							{
								if (!String.IsNullOrEmpty(receiver.Data))
								{
									typeName = receiver.Data;
								}
								else
								{
									typeName = item.ContentType.Name;
								}
								deleteItemTypes.Add(item.ContentType.Name, typeName);
								break;
							}
						}
					}
				}

				if (String.IsNullOrEmpty(typeName))
				{
					if (deleteItemTypes.ContainsKey(item.ParentList.Title))
					{
						typeName = deleteItemTypes[item.ParentList.Title];
					}
					else if (item.ParentList.EventReceivers != null)
					{
						foreach (SPEventReceiverDefinition receiver in item.ParentList.EventReceivers)
						{
							if (receiver.Name == "SLAM" + siteName + item.ParentList.Title + Microsoft.SharePoint.SPEventReceiverType.ItemDeleting.ToString())
							{
								typeName = item.ParentList.Title;
								deleteItemTypes.Add(item.ParentList.Title, typeName);
								break;
							}
						}
					}
				}

				if (!String.IsNullOrEmpty(typeName))
				{
					SlamItemContext.TypeName = typeName;
					IItemDeleter deleter = ComponentService.GetInstance<IItemDeleter>(siteCollection, siteName, typeName);

					//the Deleter's Delete method returns true if the item should actually be deleted
					deleter.Delete(item);
				}
			}
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem)
		{
			return SlamUpdate(listItem, true);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges)
		{
			return SlamUpdate(listItem, runWithElevatedPriveleges, false);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <param name="saveItem">Indicates whether the given ListItem should be saved in SharePoint as well</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges, bool saveItem)
		{
			return SlamUpdate(listItem, runWithElevatedPriveleges, saveItem, true);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <param name="saveItem">Indicates whether the given ListItem should be saved in SharePoint as well</param>
		/// <param name="saveAsSystem">Indicates whether the given ListItem should be saved in SharePoint using SystemUpdate</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges, bool saveItem, bool saveAsSystem)
		{
			return SlamUpdate(listItem, runWithElevatedPriveleges, saveItem, saveAsSystem, (new SPFieldLookupValue(listItem["Modified By"].ToString())).LookupValue);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <param name="saveItem">Indicates whether the given ListItem should be saved in SharePoint as well</param>
		/// <param name="saveAsSystem">Indicates whether the given ListItem should be saved in SharePoint using SystemUpdate</param>
		/// <param name="updateUserName">Specific username used to update the given ListItem</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges, bool saveItem, bool saveAsSystem, string updateUserName)
		{
			return SlamUpdate(listItem, runWithElevatedPriveleges, saveItem, saveAsSystem, updateUserName, false);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <param name="saveItem">Indicates whether the given ListItem should be saved in SharePoint as well</param>
		/// <param name="saveAsSystem">Indicates whether the given ListItem should be saved in SharePoint using SystemUpdate</param>
		/// <param name="updateUserName">Specific username used to update the given ListItem</param>
		/// <param name="isNew">Indicates whether the given ListItem is new, i.e. whether it has been updated in SharePoint yet</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges, bool saveItem, bool saveAsSystem, string updateUserName, bool isNew)
		{	
			return SlamUpdate(listItem, runWithElevatedPriveleges, saveItem, saveAsSystem, updateUserName, isNew, SlamItemContext.FolderNameChanged);
		}

		/// <summary>
		/// Updates an item in SLAM's target data source.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="runWithElevatedPriveleges">Indicates whether update process should run with elevated priveleges</param>
		/// <param name="saveItem">Indicates whether the given ListItem should be saved in SharePoint as well</param>
		/// <param name="saveAsSystem">Indicates whether the given ListItem should be saved in SharePoint using SystemUpdate</param>
		/// <param name="updateUserName">Specific username used to update the given ListItem</param>
		/// <param name="isNew">Indicates whether the given ListItem is new, i.e. whether it has been updated in SharePoint yet</param>
		/// <param name="updateFolderChildren">If the given item is a folder, indicates whether to update the children of that folder recursively</param>
		/// <returns>Indication whether update succeeded</returns>
		public static bool SlamUpdate(this SPListItem listItem, bool runWithElevatedPriveleges, bool saveItem, bool saveAsSystem, string updateUserName, bool isNew, bool updateFolderChildren)
		{
			bool returned = true;
			EventUtility eventUtility = new EventUtility();
			eventUtility.DisableEventHandlers();
			
			bool disposeSites = false;
			SPSite siteCollection = null;
			SPWeb site = listItem.Web;
			SPList list = listItem.ParentList;
			SPListItem listItemToUpdate = listItem;

			if (runWithElevatedPriveleges || !list.Fields.ContainsField("SLAM Properties"))
			{
				SPSecurity.RunWithElevatedPrivileges(delegate()
				{
					bool itemFound = false;

					siteCollection = new SPSite(site.Site.ID);
					site = siteCollection.AllWebs[site.ID];
					site.AllowUnsafeUpdates = true;
					list = site.Lists[listItem.ParentList.ID];
					try
					{
						listItemToUpdate = list.GetItemById(listItem.ID);
						itemFound = true;
						list.AddSlamPropertiesField();
					}
					catch { }

					disposeSites = true;

					if (itemFound)
					{
						foreach (SPField field in listItem.Fields)
						{
							object firstValue = null;
							try
							{
								firstValue = listItem[field.Id];
							}
							catch { }
							object secondValue = null;
							try
							{
								secondValue = listItemToUpdate[field.Id];
							}
							catch { }
							if (!field.ReadOnlyField && listItemToUpdate.Fields.Contains(field.Id) && Convert.ToString(firstValue) != Convert.ToString(secondValue))
							{
								listItemToUpdate[field.Id] = listItem[field.Id];
							}
						}

						if (listItemToUpdate.ModerationInformation != null && listItemToUpdate.ModerationInformation.Status != listItem.ModerationInformation.Status)
						{
							listItemToUpdate.ModerationInformation.Status = listItem.ModerationInformation.Status;
						}
					}
				});
			}
			else
			{
				siteCollection = site.Site;
				list = listItem.ParentList;
			}

			listItemToUpdate.SetSlamProperty("LastUpdated", DateTime.Now.ToString(DateTimeFormatInfo.InvariantInfo));
			
			if (saveItem)
			{
				listItemToUpdate.Update();

				SPUser user = site.EnsureUser(updateUserName);
				listItemToUpdate[SPBuiltInFieldId.Editor] = new SPFieldUserValue(site, user.ID, user.Name);
				listItemToUpdate.UpdateOverwriteVersion();
			}
			else if (saveAsSystem)
			{
				listItemToUpdate.SystemUpdate(true);
			}
			else
			{
				listItemToUpdate.SystemUpdate(false);
			}

			SPListItem currentListItem = SlamItemContext.ListItem;
			string currentUserLoginName = SlamItemContext.UserLoginName;
			string currentTypeName = SlamItemContext.TypeName;
			bool currentProcessedByContentType = SlamItemContext.ProcessedByContentType;

			SlamItemContext.ListItem = listItemToUpdate;
			SlamItemContext.UserLoginName = updateUserName;
			
			ConfiguredType configuredType = listItemToUpdate.GetSlamConfiguredType();
			SlamItemContext.ProcessedByContentType = configuredType.ContentType;

			if (configuredType.ContentType)
			{
				string contentTypeName = listItemToUpdate.ContentType.Name;
				if (configuredType != null)
				{
					contentTypeName = configuredType.Name;
				}
				SlamItemContext.TypeName = contentTypeName;
			}
			else
			{
				SlamItemContext.TypeName = list.Title;
			}

			if (isNew)
			{
				IItemCreator creator = null;
				try
				{
					if (SlamContext.Cache.ContainsKey(Constants.Cache.ItemCreatorCollection, CacheScope.Application))
					{
						var creators = SlamContext.Cache.Get<Dictionary<string, IItemCreator>>(Constants.Cache.ItemCreatorCollection);
						if (creators.ContainsKey(listItemToUpdate.ParentList.ID.ToString()))
							creator = creators[listItem.ParentList.ID.ToString()];
						else if (listItemToUpdate.ContentType != null && creators.ContainsKey(listItemToUpdate.ContentType.Parent.Id.ToString()))
							creator = creators[listItemToUpdate.ContentType.Parent.Id.ToString()];
					}
				}
				catch { }

				if (creator == null)
					creator = ComponentService.GetInstance<IItemCreator>(siteCollection.ServerRelativeUrl.TrimStart('/'), site.Name, SlamItemContext.TypeName);
				if (creator != null)
					returned = creator.Create(listItemToUpdate);
			}
			else
			{
				IItemUpdater updater = null;
				try
				{
					if (SlamContext.Cache.ContainsKey(Constants.Cache.ItemUpdaterCollection, CacheScope.Application))
					{
						var updaters = SlamContext.Cache.Get<Dictionary<string, IItemUpdater>>(Constants.Cache.ItemUpdaterCollection);
						if (updaters.ContainsKey(listItemToUpdate.ParentList.ID.ToString()))
							updater = updaters[listItem.ParentList.ID.ToString()];
						else if (listItemToUpdate.ContentType != null && updaters.ContainsKey(listItemToUpdate.ContentType.Parent.Id.ToString()))
							updater = updaters[listItemToUpdate.ContentType.Parent.Id.ToString()];
					}
				}
				catch { }

				if (updater == null)
					updater = ComponentService.GetInstance<IItemUpdater>(siteCollection.ServerRelativeUrl.TrimStart('/'), site.Name, SlamItemContext.TypeName);
				if (updater != null)
					returned = updater.Update(listItemToUpdate);
			}

			//If the ListItem is a Folder, update its children
			if (listItemToUpdate.Folder != null && updateFolderChildren)
			{
				Dictionary<string, string> updateItemTypes = new Dictionary<string, string>();
				UpdateFolder(siteCollection.ServerRelativeUrl.TrimStart('/'), site.Name, listItemToUpdate, updateItemTypes);
			}

			if (disposeSites)
			{
				site.Dispose();
				siteCollection.Dispose();
			}

			SlamItemContext.ListItem = currentListItem;
			SlamItemContext.UserLoginName = currentUserLoginName;
			SlamItemContext.TypeName = currentTypeName;
			SlamItemContext.ProcessedByContentType = currentProcessedByContentType;

			eventUtility.EnableEventHandlers();
			return returned;
		}

		private static void UpdateFolder(string siteCollection, string siteName, SPListItem item, Dictionary<string, string> updateItemTypes)
		{
			if (item != null)
			{
				SPFolder folder = item.Folder;
				if (folder != null)
				{
					foreach (SPFolder subFolder in folder.SubFolders)
					{
						UpdateFolder(siteCollection, siteName, subFolder.Item, updateItemTypes);
						UpdateFolderItem(siteCollection, siteName, subFolder.Item, updateItemTypes);
					}

					foreach (SPFile file in folder.Files)
					{
						UpdateFolderItem(siteCollection, siteName, file.Item, updateItemTypes);
					}
				}
			}
		}

		private static void UpdateFolderItem(string siteCollection, string siteName, SPListItem item, Dictionary<string, string> updateItemTypes)
		{
			if (item != null)
			{
				string typeName = "";
				string eventReceiverName = "";
				SlamItemContext.ListItem = item;

				if (item.ContentType != null)
				{
					if (updateItemTypes.ContainsKey(item.ContentType.Name))
					{
						typeName = updateItemTypes[item.ContentType.Name];
					}
					else
					{
						SPEventReceiverDefinitionCollection eventReceivers = null;
						if (item.ContentType.EventReceivers != null)
						{
							eventReceivers = item.ContentType.EventReceivers;
							eventReceiverName = "SLAM" + siteName + item.ContentType.Name + Microsoft.SharePoint.SPEventReceiverType.ItemUpdated.ToString();
						}
						else
						{
							eventReceivers = item.ParentList.EventReceivers;
							eventReceiverName = "SLAM" + item.ContentType.Name + Microsoft.SharePoint.SPEventReceiverType.ItemUpdated.ToString();
						}
						if (eventReceivers != null)
						{
							foreach (SPEventReceiverDefinition receiver in eventReceivers)
							{
								if (receiver.Name == eventReceiverName)
								{
									if (!String.IsNullOrEmpty(receiver.Data))
									{
										typeName = receiver.Data;
									}
									else
									{
										typeName = item.ContentType.Name;
									}
									updateItemTypes.Add(item.ContentType.Name, typeName);
									break;
								}
							}
						}
					}
				}

				if (String.IsNullOrEmpty(typeName))
				{
					if (updateItemTypes.ContainsKey(item.ParentList.Title))
					{
						typeName = updateItemTypes[item.ParentList.Title];
					}
					else
					{
						SPEventReceiverDefinitionCollection eventReceivers = item.ParentList.EventReceivers;
						if (eventReceivers != null)
						{
							foreach (SPEventReceiverDefinition receiver in eventReceivers)
							{
								if (receiver.Name == "SLAM" + siteName + item.ParentList.Title + Microsoft.SharePoint.SPEventReceiverType.ItemUpdated.ToString())
								{
									typeName = item.ParentList.Title;
									updateItemTypes.Add(item.ParentList.Title, typeName);
									break;
								}
							}
						}
					}
				}

				if (!String.IsNullOrEmpty(typeName))
				{
					SlamItemContext.TypeName = typeName;
					IItemUpdater updater = ComponentService.GetInstance<IItemUpdater>(siteCollection, siteName, typeName);
					updater.Update(item);
				}
			}
		}

		/// <summary>
		/// Verifies whether the given item has been slammed since the last time it was modified.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <returns>Indicates whether the given ListItem requires updating in SLAM's target data source</returns>
		public static bool VerifyItemRequiresSlamUpdate(this SPListItem listItem)
		{
			string updatedDateString = listItem.GetSlamPropertyValue("LastUpdated");
			return !(!String.IsNullOrEmpty(updatedDateString) && (DateTime.Parse(updatedDateString).CompareTo(DateTime.Parse(listItem["Modified"].ToString())) >= 0));
		}
	}
}
